function isObject(target: any): target is object {
  return Object.prototype.toString.call(target) === '[object Object]'
}

/**
 * 无效的值：null、undefined、''、[]、{}、NaN
 * @param value 值
 */
function isUnvalid(value: any): value is null | undefined | '' | [] | object {
  if (value === null || value === undefined) return true

  const valueType = typeof value
  if ((valueType === 'string' || Array.isArray(value)) && !value.length) return true

  if (valueType === 'number' && Number.isNaN(value)) return true

  if (isObject(value) && !Object.keys(value).length) return true

  return false
}

/**
 * 数字正则
 */
const NumberRegExp = /^-?(\d|[1-9]\d+|\d\.\d+|[1-9]\d+\.\d+)$/

/**
 * 浮点数正则
 */
const FloatNumberRegExp = /^-?(\d\.\d+|[1-9]\d*\.\d+)$/

interface NumberValidatorOptionsBase {
  /** 最小值限制 */
  min?: number
  /** 最大值限制 */
  max?: number
  /** 必填 */
  required?: boolean
  /**
   * 是否进行安全整数校验
   * @default true
   * @see https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger
   */
  safe?: boolean
}

export interface FloatValidatorOptions extends NumberValidatorOptionsBase {
  /** 校验/格式化的目标为浮点数 */
  mode: 'float'
  /** 精度 */
  precision?: number
}

export interface IntegerValidatorOptions extends NumberValidatorOptionsBase {
  /** 校验/格式化的目标为整数 */
  mode: 'integer'
}

export type NumberValidatorOptions = FloatValidatorOptions | IntegerValidatorOptions

/**
 * 数字字符串的校验结果
 */
export enum FLOAT_VALIDATOR_RESULT {
  /** 有效的 */
  VALID = 0,
  /** 无效的 */
  UNVALID = 1,
  /** 非安全的数 */
  UNSAFE = 2,
  /** 大于最大值 */
  GREATER_THAN_MAX_VALUE = 3,
  /** 小于最小值 */
  LESS_THAN_MIN_VALUE = 4,
  /** 精度过大 */
  EXCESSIVE_PRECISION = 5
}

/**
 * 数字字符串的校验结果
 */
export enum INTEGER_VALIDATOR_RESULT {
  /** 有效的 */
  VALID = 10,
  /** 无效的 */
  UNVALID = 11,
  /** 非安全的数 */
  UNSAFE = 12,
  /** 大于最大值 */
  GREATER_THAN_MAX_VALUE = 13,
  /** 小于最小值 */
  LESS_THAN_MIN_VALUE = 14,
  /** 是浮点数 */
  FLOAT = 15
}

export type FloatValidatorMessage = {
  [K in FLOAT_VALIDATOR_RESULT]?: string
}

export type IntegerValidatorMessage = {
  [K in INTEGER_VALIDATOR_RESULT]?: string
}

export enum NumberValidatorMessageExtension {
  /** min、max 同时存在 */
  MINMAX = 'minmax',
  DYNAMIC_MINMAX = 'dynamic-minmax'
}

/**
 * 判断校验结果是否为“有效的”
 * @param result 校验结果
 */
export function numberValidatorResultIsValid(
  result: INTEGER_VALIDATOR_RESULT | FLOAT_VALIDATOR_RESULT
): result is INTEGER_VALIDATOR_RESULT.VALID | FLOAT_VALIDATOR_RESULT.VALID {
  return result === INTEGER_VALIDATOR_RESULT.VALID || result === FLOAT_VALIDATOR_RESULT.VALID
}

const templateRegExp = /{(min|max|precision)}/g

/**
 * 使用错误信息的模板来生成最终的结果
 * @param template 消息模板
 * @param options 校验边界
 */
function assembleMessage(template: string, options: NumberValidatorOptions) {
  return template.replace(templateRegExp, (match, key: keyof IntegerValidatorOptions) => {
    return (options[key] ?? match) as string
  })
}

/* ------------------------------------------------------------------------------------------ 整数 ------------------------------------------------------------------------------------------ */

export function integerValidator(value: any, options: NumberValidatorOptions): INTEGER_VALIDATOR_RESULT {
  if (value === undefined || value === null || value === '') {
    if (options.required) return INTEGER_VALIDATOR_RESULT.UNVALID
    return INTEGER_VALIDATOR_RESULT.VALID
  }

  value = String(value)

  if (!NumberRegExp.test(value)) return INTEGER_VALIDATOR_RESULT.UNVALID
  if (FloatNumberRegExp.test(value)) return INTEGER_VALIDATOR_RESULT.FLOAT

  const numberValue = parseInt(value)

  const { min } = options
  if ((min || min === 0) && numberValue < min) return INTEGER_VALIDATOR_RESULT.LESS_THAN_MIN_VALUE

  const { max } = options
  if ((max || max === 0) && numberValue > max) return INTEGER_VALIDATOR_RESULT.GREATER_THAN_MAX_VALUE

  if (options.safe != false && !Number.isSafeInteger(numberValue)) return INTEGER_VALIDATOR_RESULT.UNSAFE

  return INTEGER_VALIDATOR_RESULT.VALID
}

/**
 * 整数校验结果的默认提示文字
 */
const defaultIntegerNumberMessages: Required<IntegerValidatorMessage> = {
  [INTEGER_VALIDATOR_RESULT.VALID]: '',
  [INTEGER_VALIDATOR_RESULT.UNVALID]: '请输入整数',
  [INTEGER_VALIDATOR_RESULT.UNSAFE]: '请输入 ±(2^53 - 1) 范围内的整数',
  [INTEGER_VALIDATOR_RESULT.GREATER_THAN_MAX_VALUE]: '请输入小于等于 {max} 的整数',
  [INTEGER_VALIDATOR_RESULT.LESS_THAN_MIN_VALUE]: '请输入大于等于 {min} 的整数',
  [INTEGER_VALIDATOR_RESULT.FLOAT]: '请输入整数'
}

const extentionIntegerNumberMessages: Record<NumberValidatorMessageExtension, IntegerValidatorMessage> = {
  [NumberValidatorMessageExtension.MINMAX]: {
    [INTEGER_VALIDATOR_RESULT.UNVALID]: '请输入 {min}~{max} 的整数',
    [INTEGER_VALIDATOR_RESULT.GREATER_THAN_MAX_VALUE]: '请输入 {min}~{max} 的整数',
    [INTEGER_VALIDATOR_RESULT.LESS_THAN_MIN_VALUE]: '请输入 {min}~{max} 的整数',
    [INTEGER_VALIDATOR_RESULT.FLOAT]: '请输入 {min}~{max} 的整数'
  },
  [NumberValidatorMessageExtension.DYNAMIC_MINMAX]: {}
}

function getIntegerMessageText(
  result: INTEGER_VALIDATOR_RESULT,
  message?: IntegerValidatorMessage | NumberValidatorMessageExtension
): string {
  if (!message) return defaultIntegerNumberMessages[result]
  return (
    (isObject(message) ? message : extentionIntegerNumberMessages[message])[result] ??
    defaultIntegerNumberMessages[result]
  )
}

/**
 * 直接校验值并返回一个 Promise
 * @param value 校验的值
 * @param options 校验的边界信息
 * @param message 校验失败的错误信息
 */
export function integerValidatorAsync(
  value: any,
  options: IntegerValidatorOptions,
  message?: IntegerValidatorMessage | NumberValidatorMessageExtension
): Promise<void> {
  const result = integerValidator(value, options)

  if (numberValidatorResultIsValid(result)) return Promise.resolve()

  if (
    message === NumberValidatorMessageExtension.DYNAMIC_MINMAX &&
    !isUnvalid(options.min) &&
    !isUnvalid(options.max)
  ) {
    message = NumberValidatorMessageExtension.MINMAX
  }

  return Promise.reject(assembleMessage(getIntegerMessageText(result, message), options))
}

/* ------------------------------------------------------------------------------------------ 浮点 ------------------------------------------------------------------------------------------ */

const PrecisionRegExpMap = new Map<number, RegExp>()

function normalizeFloatPrecisionRegExp(precision: FloatValidatorOptions['precision']): RegExp | undefined {
  if (typeof precision === 'number' && precision > 0) {
    if (!PrecisionRegExpMap.has(precision))
      PrecisionRegExpMap.set(precision, new RegExp(`^-?\\d+\\.\\d{1,${precision}}$`))
    return PrecisionRegExpMap.get(precision)
  }
  return void 0
}

export function floatValidator(value: any, options: FloatValidatorOptions): FLOAT_VALIDATOR_RESULT {
  if (value === undefined || value === null || value === '') {
    if (options.required) return FLOAT_VALIDATOR_RESULT.UNVALID
    return FLOAT_VALIDATOR_RESULT.VALID
  }

  value = String(value)

  if (!NumberRegExp.test(value)) return FLOAT_VALIDATOR_RESULT.UNVALID

  const numberValue = parseFloat(value)

  const { min } = options
  if ((min || min === 0) && numberValue < min) return FLOAT_VALIDATOR_RESULT.LESS_THAN_MIN_VALUE

  const { max } = options
  if ((max || max === 0) && numberValue > max) return FLOAT_VALIDATOR_RESULT.GREATER_THAN_MAX_VALUE

  const precisionRegExp = normalizeFloatPrecisionRegExp(options.precision)
  if (precisionRegExp && FloatNumberRegExp.test(value) && !precisionRegExp.test(value))
    return FLOAT_VALIDATOR_RESULT.EXCESSIVE_PRECISION

  if (options.safe != false && !Number.isSafeInteger(parseInt(value))) return FLOAT_VALIDATOR_RESULT.UNSAFE

  return FLOAT_VALIDATOR_RESULT.VALID
}

/**
 * 浮点数校验结果的默认提示文字
 */
const defaultFloatNumberMessages: Required<FloatValidatorMessage> = {
  [FLOAT_VALIDATOR_RESULT.VALID]: '',
  [FLOAT_VALIDATOR_RESULT.UNVALID]: '请输入数字',
  [FLOAT_VALIDATOR_RESULT.UNSAFE]: '请输入 ±(2^53 - 1) 范围内的数字',
  [FLOAT_VALIDATOR_RESULT.GREATER_THAN_MAX_VALUE]: '请输入小于等于 {max} 的数字',
  [FLOAT_VALIDATOR_RESULT.LESS_THAN_MIN_VALUE]: '请输入大于等于 {min} 的数字',
  [FLOAT_VALIDATOR_RESULT.EXCESSIVE_PRECISION]: '请将小数位限制在 {precision} 位'
}

const extentionFloatNumberMessages: Record<NumberValidatorMessageExtension, FloatValidatorMessage> = {
  [NumberValidatorMessageExtension.MINMAX]: {
    [FLOAT_VALIDATOR_RESULT.UNVALID]: '请输入 {min}~{max} 的数字',
    [FLOAT_VALIDATOR_RESULT.GREATER_THAN_MAX_VALUE]: '请输入 {min}~{max} 的数字',
    [FLOAT_VALIDATOR_RESULT.LESS_THAN_MIN_VALUE]: '请输入 {min}~{max} 的数字'
  },
  [NumberValidatorMessageExtension.DYNAMIC_MINMAX]: {}
}

function getFloatMessageText(
  result: FLOAT_VALIDATOR_RESULT,
  message?: FloatValidatorMessage | NumberValidatorMessageExtension
): string {
  if (!message) return defaultFloatNumberMessages[result]
  return (
    (isObject(message) ? message : extentionFloatNumberMessages[message])[result] ?? defaultFloatNumberMessages[result]
  )
}

/**
 * 直接校验值并返回一个 Promise
 * @param value 校验的值
 * @param options 校验的边界信息
 * @param message 校验失败的错误信息
 */
export function floatValidatorAsync(
  value: any,
  options: FloatValidatorOptions,
  message: FloatValidatorMessage | NumberValidatorMessageExtension = NumberValidatorMessageExtension.DYNAMIC_MINMAX
): Promise<void> {
  const result = floatValidator(value, options)

  if (numberValidatorResultIsValid(result)) return Promise.resolve()

  if (
    message === NumberValidatorMessageExtension.DYNAMIC_MINMAX &&
    !isUnvalid(options.min) &&
    !isUnvalid(options.max)
  ) {
    message = NumberValidatorMessageExtension.MINMAX
  }

  return Promise.reject(assembleMessage(getFloatMessageText(result, message), options))
}

/* ------------------------------------------------------------------------------------------ 组合 ------------------------------------------------------------------------------------------ */

export function numberIsValid<Options extends FloatValidatorOptions | IntegerValidatorOptions>(
  value: any,
  options: Options
) {
  const result = options.mode === 'float' ? floatValidator(value, options) : integerValidator(value, options)
  return numberValidatorResultIsValid(result)
}

type NumberValidatorAsyncMessage<Options extends FloatValidatorOptions | IntegerValidatorOptions> =
  Options extends FloatValidatorOptions ? FloatValidatorMessage : IntegerValidatorMessage

export function numberValidatorAsync<Options extends FloatValidatorOptions | IntegerValidatorOptions>(
  value: any,
  options: Options,
  message:
    | NumberValidatorAsyncMessage<Options>
    | NumberValidatorMessageExtension = NumberValidatorMessageExtension.DYNAMIC_MINMAX
): Promise<void> {
  return options.mode === 'float'
    ? floatValidatorAsync(value, options, message as any)
    : integerValidatorAsync(value, options, message as any)
}
